using UnityEngine;
using Math;
using Noise;
using Render;

namespace Terrain
{
    public class HeightData
    {
        public int TexSize;
        public float OneOverTexSize;
        public Texture2D[] HeightMaps;
    }

    public static class HeightMap
    {
        public static float UVScale = 6.0f;
        public static float UVShift = 0.0f;
        public static float NoiseScale = 0.5f;
        public static float NoiseShift = 0.5f;
        public static int Octaves = 1;
        public static float Lacunarity = 1.92f;
        public static float Gain = 0.5f;
        public static float Depth = 0.0f;

        public static void Create(int n, out HeightData data)
        {
            data                        = new HeightData();
            data.TexSize                = n+1;
            data.OneOverTexSize         = 1.0f/data.TexSize;
        }

        public static void CreateHeights(FilterMode filterMode, int dimensions, int levels, IVector2[] levelOffsets, out Texture2D[] heights)
        {
            heights = new Texture2D[levels];

            for (int i=0; i<levels; i++)
            {
                heights[i] = new Texture2D(dimensions, dimensions, TextureFormat.RGBA32, false, false);
                UpdateHeights(filterMode, dimensions, i, levelOffsets[i], heights[i]);
            }
        }

        public static void UpdateHeights(FilterMode filterMode, int dimensions, int level, IVector2 levelOffset, Texture2D texture)
        {
            Color[] colors = new Color[dimensions*dimensions];
            for (int z=0; z<dimensions; z++)
                for (int x=0; x<dimensions; x++)
                    colors[(z*dimensions)+x] = GetHeight(levelOffset.x + (x*(1<<level)), levelOffset.y + (z*(1<<level)), dimensions);

            texture.SetPixels(colors);
            texture.filterMode = filterMode;
            texture.wrapMode = TextureWrapMode.Repeat;
            texture.Apply(false);
        }

        public static void UpdateHeights(IVector2 from, IVector2 to, int dimensions, int level, IVector2 levelOffset, Texture2D texture)
        {
            // Texture Space : (j & modTexSize, k & modTexSize) 
            // World Space : ( levelOffset.x + j * (1<<level), levelOffset.y + k * (1<<level) )



            // const float po = powf( 2.0f, float(Level) );
            // UINT modTexSize = _texSize - 1;

            // Texture Space : (j & modTexSize, k & modTexSize) 
            // World Space : ( _Clipmaps[Level].clipShift.x + j * po, _Clipmaps[Level].clipShift.y + k * po )

            // height[i][l] = GetWorldHeight(
            //     _Clipmaps[Level].clipShift.x + ((j+l) * po),
            //     _Clipmaps[Level].clipShift.y + (((Dir.y > 0) * po) * _texSize) + ((k+i) * po));

            if (from==to)
                return;

            IVector2 size = new IVector2(dimensions, dimensions);
            IVector2 move = to-from;

            IVector2 min = default(IVector2);
            IVector2 max = default(IVector2);

            if (move.x>0)
            {
                min.x = from.x+size.x;
                max.x = from.x+size.x+move.x;
            }
            else
            {
                min.x = to.x;
                max.x = from.x;
            }

            if (move.y>0)
            {
                min.y = from.y+size.y;
                max.y = from.y+size.y+move.y;
            }
            else
            {
                min.y = to.y;
                max.y = from.y;
            }

            //if (move.x!=0 || move.y!=0)
            //    Debug.Log(string.Format("min{0}, max{1}, from{2}, to{3}, move{4}", min, max, from, to, move));

            // Horizontal-slice
            int col_min = to.x;
            int col_max = to.x+size.x;
            for (int y=min.y; y<max.y; y++)
                for (int x=col_min; x<col_max; x++)
                    texture.SetPixel(x%dimensions, y%dimensions, GetHeight(levelOffset.x + (x*(1<<level)), levelOffset.y + (y*(1<<level)), dimensions));
            texture.Apply(false);

            // vertical-slice
            int row_min = 0;
            int row_max = 0;
            if (move.y>0)
            {
                row_min = max.y-size.y;
                row_max = min.y;
            }
            else
            {
                row_min = max.y;
                row_max = min.y+size.y;
            }
            for (int y=row_min; y<row_max; y++)
                for (int x=min.x; x<max.x; x++)
                    texture.SetPixel(x%dimensions, y%dimensions, GetHeight(levelOffset.x + (x*(1<<level)), levelOffset.y + (y*(1<<level)), dimensions));
            texture.Apply(false);
        }

        private static Color GetHeight(int x, int z, int dimensions)
        {
            float u = (((float)x)+0.5f)/dimensions;
            float v = (((float)z)+0.5f)/dimensions;
            float n = Perlin.Noise2(u*UVScale+UVShift, v*UVScale+UVShift, Octaves, Lacunarity, Gain)*NoiseScale+NoiseShift;
            float c = Colors.Saturate(n);
            return new Color(c, 0.0f, 0.0f, 1.0f);
        }
    }
}